/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <wrl.h>

#include <baja/common.hpp>
#include <baja/error.hpp>

#include <boost/random/normal_distribution.hpp>
#include <boost/random/uniform_real_distribution.hpp>
#include <boost/random/mersenne_twister.hpp>

#ifdef BAJA_WIN32
#include <Windows.h>
#endif

namespace baja {

#ifdef BAJA_WIN32

inline std::string wstrtostr(const std::wstring &wstr)
{
    // Convert a Unicode string to an ASCII string
    std::string strTo;
    std::vector<char> szTo(wstr.length() + 1);
    szTo[wstr.size()] = '\0';
    ::WideCharToMultiByte(CP_ACP, 0, wstr.c_str(), -1, szTo.data(), (int)wstr.length(), NULL, NULL);
    strTo = szTo.data();
    return strTo;
}

inline std::wstring strtowstr(const std::string &str)
{
    // Convert an ASCII string to a Unicode String
    std::wstring wstrTo;
    std::vector<wchar> wszTo(str.length() + 1);
    wszTo[str.size()] = L'\0';
    MultiByteToWideChar(CP_ACP, 0, str.c_str(), -1, wszTo.data(), (int)str.length());
    wstrTo = wszTo.data();
    return wstrTo;
}

#endif // BAJA_WIN32

class withFinally
{
    typedef std::function<void()> code_block;
public:
    withFinally(const code_block& f) : _f(f) 
    {
        BAJA_CHECK_ARG(f != nullptr);
    }
    ~withFinally() { _f(); }

    static void run(const code_block& c, const code_block& f)
    {
        withFinally run(f);
        c();
    }

private:
    code_block _f;
};

struct strings
{
    // TEST:
    template <class Tchar> inline static size_t 
    copy(
        const std::basic_string<Tchar, std::char_traits<Tchar>, std::allocator<Tchar>>& src,
        Tchar* dest, 
        const size_t dest_size, 
        size_t offset = 0, 
        long long count = -1
        )
    {
        if (dest == NULL || dest_size == 0)
            return 0;

        // adjust length of copy to fit into dest string
        size_t stringLength = src.length();
        size_t charsToCopy = min(dest_size-1, min(stringLength - offset, static_cast<size_t>(count)));
        if (offset < stringLength)
        {
            const Tchar* s = src.c_str();
            std::memcpy(dest, &s[offset], charsToCopy * sizeof(Tchar));
        }
    
        // null terminate
        dest[charsToCopy] = '\0';
        return charsToCopy;
    }
};

/*
// DEADCODE:
template <Ttarget, Tsource>
std::weak_ptr<Ttarget>
static_pointer_cast(const std::weak_ptr<Tsource>& wp_src)
{
    std::shared_ptr<Tsource> sp_src(wp_src.lock());
    BAJA_VERIFY(wp_src.expired() == !sp_src);

    std::shared_ptr<Ttarget> sp_trg(static_pointer_cast<Ttarget>(sp_src));

    return sp_trg;
}

template <Ttarget, Tsource>
std::weak_ptr<Ttarget>
dynamic_pointer_cast(const std::weak_ptr<Tsource>& wp_src)
{
    std::shared_ptr<Tsource> sp_src(wp_src.lock());
    BAJA_VERIFY(wp_src.expired() == !sp_src);

    std::shared_ptr<Ttarget> sp_trg(dynamic_pointer_cast<Ttarget>(sp_src));

    return sp_trg;
}
*/

// this is used to be able to use shared_ptr for stack allocated objects
template <typename T> struct null_deleter
{
    void operator()(T *) const
    {
        // noop
    }
};

// use this to get a shared_ptr for a stack object
template <typename T> 
inline std::shared_ptr<T> make_stack_shared_ptr(T* p) 
{ 
    return std::shared_ptr<T>(p, null_deleter<T>()); 
}

inline const float32 rand(const float32 min, const float32 max)
{
    static boost::random::mt19937 range(static_cast<unsigned int>(std::time(0))); 
    boost::random::uniform_real_distribution<float32> dist(min, max); 
    return static_cast<float32>(dist(range)); 
}

inline const float32 randNormal(const float32 mean, const float32 dev)
{
    static boost::random::mt19937 range(static_cast<unsigned int>(std::time(0))); 
    boost::random::normal_distribution<float32> dist(mean, dev); 
    return static_cast<float32>(dist(range)); 
}

//
// implement this interface as a way to provide COM interfaces from a baja interface
//
struct iunknown_provider : public ibase
{
    BAJA_INTERFACE(iunknown_provider, "E9801E7D-BF50-4bb9-951B-394BBA0F2FB4")
    virtual size_t getInterfaceCount() = 0;
    virtual Microsoft::WRL::ComPtr<IUnknown> getInterface(size_t index) = 0;
};


//
// template helper function to access a specific COM inteface from a baja interface
//
template <typename BajaInterface, typename ComInterface>
inline Microsoft::WRL::ComPtr<ComInterface> bajaInterfaceToCom(
    const std::shared_ptr<BajaInterface>& bajaObject
    )
{
    BAJA_CHECK_ARG(bajaObject != nullptr);
    bool succeeded = false;

    Microsoft::WRL::ComPtr<ComInterface> punk;

    std::shared_ptr<iunknown_provider> wrapper;

    wrapper = std::dynamic_pointer_cast<iunknown_provider, BajaInterface>(bajaObject);
    if (wrapper != nullptr)
    {
        // get count of interfaces
        size_t count = 0;
        count = wrapper->getInterfaceCount();

        // loop over all exposed d3d interfaces until we find the one we want
        for(uint32 i = 0; i < count; i++)
        {
            // get the d3d interface (as an IUnknown)
            try
            {
                WRL::ComPtr<IUnknown> unknown;
                unknown = wrapper->getInterface(i);
                if (unknown != nullptr)
                {
                    // QI into the interface we want
                    HRESULT hr = unknown.As(&punk);

                    // if all is good, break out of loop
                    if (SUCCEEDED(hr))
                    {
                        succeeded = true;
                        break;
                    }
                }
            }
            catch (...)
            {
            }
        }
    }

    if (!succeeded) throw std::logic_error("failed"); // ERROR:

    return punk;
}

} // namespace baja